//////////////////////////////////////////////////////////////////////////
#include "Application/WindowedApp.hpp"
#include "Application/Application.hpp"
#include <Config/Config.hpp>


//////////////////////////////////////////////////////////////////////////
//
Application::WindowedApp*	Application::WindowedApp::mpInstance	=	nullptr;


//////////////////////////////////////////////////////////////////////////
// Hook the config system into the filesystem.
// TODO: Yeah, this is pretty fugly....
class ConfigFileInterface : public Config::FileInterface
{
public:
	virtual void				Write( const std::string& name, const std::string& data ) override
	{
		FileSystem::File_t	confFile	=	FileSystem::Open( name, OpenMode::kWrite );
		if( confFile )
		{
			FileSystem::Write( confFile, data.c_str(), data.length() );
			FileSystem::Close( confFile );
		}
	}

	virtual const std::string	Read( const std::string& name ) override
	{
		return FileSystem::ReadFileAsString( name );
	}
}	sConfFileInterface;


//////////////////////////////////////////////////////////////////////////
Application::WindowedApp::WindowedApp( const char* argv, const std::string& companyName, const std::string& gameName )
:	mCompanyName( companyName )
,	mGameName( gameName )
,	mFileSystem( argv, companyName, gameName, "zip" )
,	mWindowManager( this, mScript )
{
	assert( mpInstance==nullptr );
	mpInstance	=	this;
}


Application::WindowedApp::~WindowedApp()
{
	assert( mpInstance==this );
	mpInstance	=	nullptr;
}


int Application::WindowedApp::Run()
{
	Config::Initializer		initConfig( mGameName + ".json", &sConfFileInterface );

	if( _InitScript() && mWindowManager.Initialize() && Initialize() )
	{
		RegisterUpdate( "Configuration Update", -100, [&]{ initConfig.Update(); } );
		while( Update() )
		{
			_RunRegisteredUpdates();
			WindowManager().RunPostCalls();
		}
	}
	return Shutdown() ? mWindowManager.Shutdown() : -1;
}




bool Application::WindowedApp::Update()
{
	if( WindowManager().BeginDraw() )
	{
		if( WindowManager().Update() )
		{
			WindowManager().EndDraw();
			return true;
		}
	}

	return false;
}


bool Application::WindowedApp::_RunGameScript()
{
	if( mScript.Run( std::string( "Script/" ) + mGameName + ".lua" ) )
		return mScript.Call( mGameName );
	return false;
}


void Application::WindowedApp::_RunRegisteredUpdates()
{
	for( auto& priorities : mUpdateMap )
	{
		for( auto& update : priorities.second )
		{
			update.second();
		}
	}
	_RemoveUpdates();
}


//////////////////////////////////////////////////////////////////////////
bool Application::WindowedApp::RegisterUpdate( const std::string& name, int32_t pri, Update_t fnc )
{
	UpdateMap_t::iterator	group	=	mUpdateMap.find( pri );
	if( group==mUpdateMap.end() )
	{
		UpdateGroup_t	group;
		group[ name ]		=	fnc;
		mUpdateMap.insert( UpdateMap_t::value_type( pri, group ) );
		return true;
	}
	else
	{
		UpdateGroup_t&	updateGroup	=	group->second;
		if( updateGroup.find( name )==updateGroup.end() )
		{
			updateGroup.insert( UpdateGroup_t::value_type( name, fnc ) );
			return true;
		}
		else
		{
			updateGroup.find( name )->second	=	fnc;
			return true;
		}
	}
	return false;
}


bool Application::WindowedApp::UnregisterUpdate( const std::string& name, int32_t pri )
{
	UpdateMap_t::iterator	group	=	mUpdateMap.find( pri );
	if( group!=mUpdateMap.end() )
	{
		UpdateGroup_t&	updateGroup	=	group->second;
		if( updateGroup.find( name ) != updateGroup.end() )
		{
			mUpdateRemovals.push_back( RemovedUpdate_t( name, pri ) );
			return true;
		}
	}
	return false;
}


void Application::WindowedApp::_RemoveUpdates()
{
	for( auto& it : mUpdateRemovals )
	{
		UpdateMap_t::iterator	group	=	mUpdateMap.find( it.second );
		if( group!=mUpdateMap.end() )
		{
			UpdateGroup_t&	updateGroup	=	group->second;
			if( updateGroup.find( it.first ) != updateGroup.end() )
			{
				updateGroup.erase( it.first );
			}
		}
	}
	mUpdateRemovals.clear();
}


//////////////////////////////////////////////////////////////////////////
// Lua integration.
#include <LuaBridge/LuaBridge.h>

using namespace luabridge;

bool Application::WindowedApp::_LuaRegisterUpdate( const std::string& name, int32_t pri, LuaRef refItem )
{
	//	luabridge::LuaRef	refItem	=	luabridge::getGlobal( mScript, name.c_str() );
	if( refItem )
	{
		return RegisterUpdate( name, pri, [=]{ refItem(); } );
	}
	return false;
}


bool Application::WindowedApp::_InitScript()
{
	getGlobalNamespace( mScript )
		.beginClass< WindowedApp >( "Application" )
		.addStaticProperty( "Instance", &Application::WindowedApp::Instance )
		//			.addStaticFunction( "Instance", &Application::WindowedApp::Instance )
		.addFunction( "RegisterUpdate", &Application::WindowedApp::_LuaRegisterUpdate )
		.addFunction( "UnregisterUpdate", &Application::WindowedApp::UnregisterUpdate )
		.endClass();

	return true;
}
